﻿using System;
using System.Collections;
using System.Data;
using System.Globalization;

namespace DataUpload.ReadFiles
{
    public delegate void RowsInsertedEventHandler(object sender, RowsInsertedEventArgs e);

    public class RowsInsertedEventArgs : EventArgs
    {
        private uint _rowsInserted;
        private uint _rowsFailed;
        private string _status;

        public uint RowsInserted
        {
            get { return _rowsInserted; }
        }

        public uint RowsFailed
        {
            get { return _rowsFailed; }
        }

        public string Status
        {
            get { return _status; }
        }

        public RowsInsertedEventArgs(uint rowsInserted, uint rowsFailed, string status)
        {
            _rowsInserted = rowsInserted;
            _rowsFailed = rowsFailed;
            _status = status;
        }
    }

    public abstract class ParsedFile: IDisposable
    {
        protected string _filePath;
        protected ArrayList _columns; // list of detected columns (with the prefix is its table name) in source file
        protected ArrayList _sourceColumns;
        protected ArrayList _targetColumns;
        protected ArrayList _targetType;
        protected MapStatus _mapStatus;
        protected uint _notifyAfter;
        protected uint _rowsInserted;
        protected SortedList _rowsFailed;
        protected bool _stopThis;

        protected CultureInfo _cultureInfo;
        protected NumberFormatInfo _numFormat;
        protected DateTimeFormatInfo _dateTimeFormat;

        protected DateTime _startUpload;
        protected DateTime _endUpload;
        protected int _totalRows = 0;
        protected int _passRows = 0;

        public enum MapStatus { NoMapping, CannotFindSpecificSource, SourceAndTargetNotMatch, SourceHasMultipleTables, ConvertTypeFailed, MappingFailed, MappingDone };

        #region Properties
        public string FilePath
        {
            get { return _filePath; }
        }

        public ArrayList Columns
        {
            get
            {
                if (_columns.Count == 0)
                    return GetColumnsList();
                return _columns;
            }
        }

        public uint NotifyAfter
        {
            get { return _notifyAfter; }
            set { _notifyAfter = value; }
        }

        public uint RowsInserted
        {
            get { return _rowsInserted; }
        }

        public SortedList RowsFailed
        {
            get { return _rowsFailed; }
        }

        public MapStatus Status
        {
            get { return _mapStatus; }
        }

        public DateTime StartUploadPoint
        {
            get
            {
                if (_startUpload != null)
                    return _startUpload;
                return DateTime.Now.AddDays(1);
            }
        }

        public DateTime EndUploadPoint
        {
            get
            {
                if (_endUpload != null)
                    return _endUpload;
                return DateTime.Now.AddDays(1);
            }
        }

        public int PercentCompleted
        {
            get
            {
                if (_totalRows == 0)
                    return 0;
                return (int)(_passRows * 100 / _totalRows);
            }
        }

        public CultureInfo FileCulture
        {
            get { return _cultureInfo; }
            set
            {
                if (value != null)
                    _cultureInfo = value;
            }
        }

        public NumberFormatInfo NumberFormatInfo
        {
            get { return _numFormat; }
            set
            {
                if (value != null)
                    _numFormat = value;
            }
        }

        public DateTimeFormatInfo DateTimeFormatInfo
        {
            get { return _dateTimeFormat; }
            set
            {
                if (value != null)
                    _dateTimeFormat = value;
            }
        }
        #endregion;

        public ParsedFile()
        {
            _columns = new ArrayList();
            _sourceColumns = null;
            _targetColumns = null;
            _targetType = null;
            _mapStatus = MapStatus.NoMapping;
            _notifyAfter = 1;
            _rowsInserted = 0;
            _rowsFailed = new SortedList();
            _stopThis = false;
            _cultureInfo = new CultureInfo("en-US", false);
            _dateTimeFormat = _cultureInfo.DateTimeFormat;
            _numFormat = _cultureInfo.NumberFormat;
        }

        public ParsedFile(string filePath)
        {
            _filePath = filePath;
            _columns = new ArrayList();
            _sourceColumns = null;
            _targetColumns = null;
            _targetType = null;
            _mapStatus = MapStatus.NoMapping;
            _notifyAfter = 1;
            _rowsInserted = 0;
            _rowsFailed = new SortedList();
            _stopThis = false;
            _cultureInfo = new CultureInfo("en-US", false);
            _dateTimeFormat = _cultureInfo.DateTimeFormat;
            _numFormat = _cultureInfo.NumberFormat;
        }

        #region Abstract functions
        public abstract ArrayList GetColumnsList();
        public abstract SortedList TakeSample(ArrayList columns, int numSample);
        public abstract void StartUpload(ref DBConnection.DBConnection connection);
        public abstract bool Clear();
        public abstract void Dispose();
        public abstract long TotalRows();
        #endregion;

        #region Virtual functions
        public virtual bool DoMapping(ArrayList sourceColumns, ArrayList targetColumns, ArrayList targetType, int samplesToCheck, out MapStatus status, out DataTable failedRow)
        {
            if (_columns == null || _columns.Count == 0)
                GetColumnsList();

            _stopThis = false;

            try
            {
                _mapStatus = FirstCheckMap(ref sourceColumns, ref targetColumns, ref targetType);
                if (_mapStatus != MapStatus.MappingDone)
                {
                    status = _mapStatus;
                    failedRow = new DataTable("temp");
                    return false;
                }

                OrganizeMap(ref sourceColumns, ref targetColumns, ref targetType);

                failedRow = new DataTable("temp");
                _mapStatus = CheckMapType(ref sourceColumns, ref targetType, samplesToCheck, ref failedRow);
                if (_mapStatus != MapStatus.MappingDone)
                {
                    status = _mapStatus;
                    return false;
                }
            }
            catch
            {
                _mapStatus = MapStatus.MappingFailed;
                status = _mapStatus;
                failedRow = new DataTable("temp");
                return false;
            }

            _sourceColumns = sourceColumns;
            _targetColumns = targetColumns;
            _targetType = targetType;
            status = _mapStatus;
            return true;
        }

        protected virtual MapStatus FirstCheckMap(ref ArrayList sourceColumns, ref ArrayList targetColumns, ref ArrayList targetType)
        {
            if (sourceColumns == null || targetColumns == null || targetType == null)
                throw new ArgumentNullException("sourceColumns || targetColumns");

            if (sourceColumns.Count != targetColumns.Count || sourceColumns.Count != targetType.Count)
                return MapStatus.SourceAndTargetNotMatch;

            foreach (string sc in sourceColumns)
            {
                if (_columns.Contains(sc) == false)
                    return MapStatus.CannotFindSpecificSource;
            }

            foreach (string t in targetType)
            {
                if (t.ToLower() != "string"
                    && t.ToLower() != "double"
                    && t.ToLower() != "datetime")
                    return MapStatus.ConvertTypeFailed;
            }

            return MapStatus.MappingDone;
        }

        protected virtual MapStatus CheckMapType(ref ArrayList sourceColumns, ref ArrayList targetType, int samplesToCheck, ref DataTable failedRow)
        {
            string action = "";
            try
            {
                action = "Sampling data to check";
                SortedList samples = TakeSample(sourceColumns, samplesToCheck);
                action = "Checking sample data";
                for (int i = 0; i < sourceColumns.Count; i++)
                {
                    if (targetType[i].ToString().ToLower() != "string")
                    {
                        ArrayList sample = (ArrayList)samples[_columns.IndexOf(sourceColumns[i])];
                        foreach (object data in sample)
                        {
                            try
                            {
                                if (targetType[i].ToString().ToLower() == "double")
                                {
                                    Double.Parse(data.ToString(), NumberStyles.Any, _numFormat);
                                }
                                else if (targetType[i].ToString().ToLower() == "datetime")
                                {
                                    DateTime.Parse(data.ToString(), _dateTimeFormat);
                                }
                            }
                            catch
                            {
                                failedRow.Columns.Add(sourceColumns[i].ToString());
                                object[] values = { data };
                                failedRow.Rows.Add(values);
                                return MapStatus.ConvertTypeFailed;
                            }
                        }
                    }
                }
            }
            catch (Exception exc)
            {
                DBConnection.MyDebugClass.LogThis(action, exc);
                throw exc;
            }
            return MapStatus.MappingDone;
        }

        protected virtual void OrganizeMap(ref ArrayList sourceColumns, ref ArrayList targetColumns, ref ArrayList targetType)
        {
            SortedList temp1 = new SortedList();
            SortedList temp2 = new SortedList();

            for (int i = 0; i < sourceColumns.Count; i++)
            {
                temp1.Add(targetColumns[i], sourceColumns[i]);
                temp2.Add(targetColumns[i], targetType[i]);
            }

            ArrayList sCols_sorted = new ArrayList();
            ArrayList tCols_sorted = new ArrayList();
            ArrayList tType_sorted = new ArrayList();

            for (int i = 0; i < temp1.Count; i++)
            {
                sCols_sorted.Add(temp1.GetByIndex(i));
                tCols_sorted.Add(temp1.GetKey(i));
                tType_sorted.Add(temp2.GetByIndex(i));
            }

            sourceColumns = sCols_sorted;
            targetColumns = tCols_sorted;
            targetType = tType_sorted;
        }

        protected virtual void UploadThis(ArrayList scripts, ref DBConnection.DBConnection connection)
        {
            string action = "";
            RowsInsertedEventArgs e;

            action = "Begin uploading";
            foreach (string cmd in scripts)
            {
                try
                {
                    if (_stopThis)
                        return;
                    action = "Executing: " + cmd;
                    if (connection.ExecNonQuery(cmd) == 1)
                        _rowsInserted++;
                }
                catch (Exception exc)
                {
                    if (action.StartsWith("Executing: "))
                    {
                        if (_rowsFailed.ContainsKey(action.Substring(11)) == false)
                            _rowsFailed.Add(action.Substring(11), exc);
                    }
                }
                finally
                {
                    if (_stopThis == false && (_rowsInserted + _rowsFailed.Count) % _notifyAfter == 0)
                    {
                        e = new RowsInsertedEventArgs(_rowsInserted, (uint)_rowsFailed.Count, "Uploading " + _filePath);
                        OnRowsInsertedEvent(this, e);
                    }
                }
            }
        }
        #endregion;

        #region Public event
        public event RowsInsertedEventHandler RowsInsertedEvent;
        protected virtual void OnRowsInsertedEvent(object sender, RowsInsertedEventArgs e)
        {
            RowsInsertedEvent(sender, e);
        }
        #endregion;

        #region Other functions
        public override string ToString()
        {
            return "ParsedFile";
        }

        public static string GetStatusDescription(MapStatus status)
        {
            if (!System.IO.File.Exists("MapStatusDescription.xml"))
                return status.ToString();
            System.Xml.XmlDocument descriptionFile = new System.Xml.XmlDocument();
            descriptionFile.Load("MapStatusDescription.xml");
            System.Xml.XmlNodeList statusName = descriptionFile.GetElementsByTagName("name");
            System.Xml.XmlNodeList statusDescription = descriptionFile.GetElementsByTagName("description");
            for (int i = 0; i < statusName.Count; i++)
            {
                if (statusName[i].InnerText == status.ToString())
                    return statusDescription[i].InnerText;
            }
            return status.ToString();
        }

        public void Stop()
        {
            _stopThis = true;
        }
        #endregion;
    }
}
